<?php
/**
 * Класс для управления деревьями по алгоритму Nested Sets
 *
 * @author     nrnwest@gmail.com
 * @version    0.1
 */
class Arm_Db_Tree_Nested
{
    /**
     * Конфигурационный объект
     *
     * @var Arm_Db_Tree_Nested_Params
     */
    private $_params = null;
    
    /**
     * Враппер
     *
     * @var Arm_Db_Tree_Nested_Sql_Pdo_Mysql
     */
    private $_sql = null;
    
    
    /**
     * Имя структурной таблицы
     *
     * @var string
     */
    private $_tableName = '';
    
    /**
     * Конструктор
     *
     * @param string $tableName имя таблицы с деревьями
     * @param Zend_Db_Adapter_Abstract $db
     * @param int $treeId идентификатор дерева
     * @param Arm_Db_Tree_Nested_Params $params
     */
    public function __construct($tableName, Zend_Db_Adapter_Abstract $db = null, $treeId = 1,  Arm_Db_Tree_Nested_Params $params = null)
    {
        if (!is_string($tableName)) {
            throw new Arm_Db_Tree_Nested_Exception('Имя таблицы может быть только переменной типа string');
        }
        if ($db === null) {
            $db = Arm_Db::getDb();
        }
        $this->_tableName = $tableName;
        if ($params !== null) {
            $this->setParams($params);
        } else {
            // Инициируем установку конфигурационного объекта
            // с параметрами по умолчанию
            // и устанавливаем идентификатор дерева
            $this->getParams()->setTreeId($treeId);
        }
        $this->_sql = Arm_Db::getSqlObject('Arm_Db_Tree_Nested_Sql', $db);
        $this->_sql->setTableName($tableName);
        $this->_sql->setParams($this->getParams());
    }
    
    /**
     * Устанавливает конфигурационный объект
     *
     * @param Arm_Db_Tree_Nested_Params $params
     */
    public function setParams(Arm_Db_Tree_Nested_Params $params)
    {
        $this->_params = $params;
    }

    /**
     * Получение конфигурационного объекта
     *
     * @return Arm_Db_Tree_Nested_Params
     */
    public function getParams()
    {
        if ($this->_params === null) {
            // Создаем объект со значениями по-умолчанию
            $params = new Arm_Db_Tree_Nested_Params();
            $this->_params = $params;
        }
        
        return $this->_params;
    }
    
    /**
     * Получение идентификаторов всех деревьев в таблице
     *
     * @return array
     */
    public function getAllTreesIds()
    {
        return $this->_sql->getAllTreesIds();
    }
    
    /**
     * Возвращает имя структурной таблицы
     *
     * @return string
     */
    public function getTableName()
    {
        return $this->_tableName;
    }
    
    /**
     * Устанавливает имя структурной таблицы
     * в текущем объекте и в объекте {@link self::$_wrapper}
     *
     * @param unknown_type $tableName
     */
    public function setTableName($tableName)
    {
        $this->_tableName = $tableName;
        if ($this->_sql !== null) {
            $this->_sql->setTableName($tableName);
        }
    }
    
    /**
     * Создаёт структурную таблицу на основе переданных параметров
     * В случае, если параметры не будут переданы, то будут
     * использованы параметры по умолчанию.
     *
     * Если массив с параметрами передан, то для успешного создания
     * структурной таблицы он должен содержать значения для
     * следующих параметров:
     *
     * table_name - имя структурной таблицы (не обязательно)
     * id         - идентификатор элемента, первичный ключ
     * tree_id    - идентификатор дерева
     * left_key   - левый ключ
     * right_key  - правый ключ
     * level      - уровень вложенности элемента
     *
     *
     * @param array $params
     * @return boolean
     */
    public function createTree($params = array())
    {
        if (empty($params)) {
            // Берем значения из объекта параметров
            $params = $this->_params->getAsColumns();
            // Устанавливаем имя таблицы для создания
            $params['table_name'] = $this->_tableName;
        } else {
            // Устанавливаем новые имена полей таблицы в
            // конфигурационный объект
            $this->_params->setColumns($params);
            if (isset($params['table_name'])) {
                // так как в параметрах могло быть указано имя таблицы, то
                // обновляем его в текущем объекте
                $this->setTableName($params['table_name']);
            } else {
                // иначе добавляем в параметры текущее имя таблицы
                $params['table_name'] = $this->_tableName;
            }
        }
        
        // Проверка на присутствие всех значений для шаблона
        // создания таблицы
        $checkParams = array_keys($this->getParams()->getColumns());
        array_push($checkParams, 'table_name');
        
        $paramsKeys = array_keys($params);
        $diff = array_diff($checkParams, $paramsKeys);
        if (!empty($diff)) {
            throw new Arm_Db_Tree_Nested_Exception('Ошибка создания '
                . 'структурной таблицы: не указаны параметры: '
                . implode(', ', $diff));
        }
        // Создаем таблицу
        $this->_sql->createTree($params);
        // Создаем корневую вершину
        $this->createRootNode();
        // Возвращаем идентификатор дерева
        return $this->getParams()->getTreeId();
    }
    
    /**
     * Получение целого дерева
     *
     * @param boolean $returnType формат возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getTree($returnType = null)
    {
        $trees = $this->getAllTreesIds();
        $treeId = $this->getParams()->getTreeId();

        if (!in_array($treeId, $trees)) {
            throw new Arm_Db_Tree_Nested_Exception('Дерево с идентификатором '
                . $treeId . ' не найдено в таблице `'
                . $this->_tableName . '`');
        }

        $select = $this->_sql->getTree($treeId);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }

        return $returnType ? $this->_sql->fetchAll($select) : $select;
    }
    
    /**
     * Получение всех деревьев в формате
     *
     * array('treeId' => array(данные дерева),...)
     *
     * @return array
     */
    public function getTrees()
    {
        $treesIds = $this->getAllTreesIds();
        $allTrees = array();
        foreach ($treesIds as $treeId) {
            $allTrees[$treeId] = $this->getTree($treeId, true);
        }
        
        return $allTrees;
    }
    
    /**
     * Получение информации об узле
     *
     * @param int $nodeId идентификатор узла
     * @param boolean $returnType тип возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getNode($nodeId, $returnType = null)
    {
        $node = $this->_sql->getNode($nodeId);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchRow($node) : $node;
    }
    
    /**
     * Получение информации о корневом узле
     * (в случае если корневых узлов несколько -
     *  информация будет получена об узле стоящем выше всех)
     *
     * @param boolean $returnType тип возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getRootNode($returnType = null)
    {
        // Самый верхний корневой обязательно имеет левый ключ 1
        $node = $this->_sql->getNodeByLeftKey(1);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchRow($node) : $node;
    }
    
    /**
     * Получение идентификатора корневого узла
     *
     * @return int
     */
    public function getRootId()
    {
        $rootNode = $this->getRootNode(true);
        if (!$rootNode) {
            throw new Arm_Db_Tree_Nested_Exception('Корневой узел '
                . ' не найден в таблице ' . $this->_tableName);
        }
        
        return $rootNode[$this->_params->getColumnAlias('id')];
    }
    
    /**
     * Получение открытого дерева
     *
     * @param int $nodeId идентификатор узла
     * @param int $returnType формат возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getOpenTree($nodeId, $returnType = null)
    {
        //$parent = $this->getParent($nodeId, true);
        
        // Отдельный вариант поведения в случае если выбран корневой элемент
        /*if (!$parent) {
            $select = $this->_sql->getRootNodes();
        } else {
            $leftKey  = $parent[$this->_params->getColumnAlias('left_key')];
            $rightKey = $parent[$this->_params->getColumnAlias('right_key')];
            $level    = $parent[$this->_params->getColumnAlias('level')];
            $select = $this->_sql->getOpenTree($leftKey, $rightKey, $level);
        }
        */
            $node = $this->getNode($nodeId, true);
            $leftKey  = $node[$this->_params->getColumnAlias('left_key')];
            $rightKey = $node[$this->_params->getColumnAlias('right_key')];
            $level    = $node[$this->_params->getColumnAlias('level')];
        $select = $this->_sql->getOpenTree($leftKey, $rightKey, $level);
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchAll($select) : $select;
    }
    
    /**
     * Получение всех узлов встречающихся на пути к заданному
     *
     * @param int $nodeId идентификатор узла
     * @param boolean $returnType
     * @return Zend_Db_Select|array
     */
    public function getPath($nodeId, $returnType = null)
    {
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        
        $select = $this->_sql->getPath($leftKey, $rightKey);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchAll($select) : $select;
    }
    
    /**
     * Получение информации о родительском узле
     *
     * @param int $nodeId
     * @param int $returnType формат возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getParent($nodeId, $returnType = null)
    {
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        $level = $node[$this->_params->getColumnAlias('level')];
        if ($level == 1) {
            // Корневой узел
            return false;
        }
        
        $select = $this->_sql->getParent($leftKey, $rightKey, $level);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchRow($select) : $select;
    }
    
    /**
     * Получение детей (на уровень ниже)
     *
     * @param int $nodeId идентификатор узла
     * @param boolean $returnType формат возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getChildren($nodeId, $returnType = null)
    {
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        $level = $node[$this->_params->getColumnAlias('level')];
        
        $select = $this->_sql->getChildren($leftKey, $rightKey, $level);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchAll($select) : $select;
    }
    
    /**
     * Получение всех детей
     *
     * @param int $nodeId идентификатор узла
     * @param boolean $returnType формат возвращаемых данных
     * @return Zend_Db_Select|array
     */
    public function getChildrens($nodeId, $returnType = null)
    {
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];

        $select = $this->_sql->getChildrens($leftKey, $rightKey);
        
        if ($returnType === null) {
            $returnType = $this->_params->getReturnType();
        }
        
        return $returnType ? $this->_sql->fetchAll($select) : $select;
    }
    
    /**
     * Статический метод определяющий по данным о ключах узлов
     * является ли один вложенным другого
     *
     * @param int $parentLeftKey левый ключ предполагаемого родителя
     * @param int $parentRightKey правый ключ предполагаемого родителя
     * @param int $leftKey левый ключ предполагаемого ребенка
     * @return boolean
     */
    public static function nodeIsChildren($parentLeftKey, $parentRightKey, $leftKey)
    {
        return (($leftKey < $parentRightKey) && ($leftKey > $parentLeftKey));
    }
    
    /**
     * Статический метод определяющий по двум ключам
     * левому и правому есть ли у такого узла вложенные узлы
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @return boolean
     */
    public static function nodeHasChildrens($leftKey, $rightKey)
    {
        return (($rightKey - $leftKey) == 1);
    }
    
    /**
     * Проверяет является ли узел дочерним по отношению к другому
     * основываясь на левом и правом ключах узлов
     *
     * @param int $childrenId проверяемый дочерний узел
     * @param int $parentId предполагаемый родительский узел
     * @return boolean
     */
    public function isChildren($childrenId, $parentId)
    {
        $parent = $this->getNode($parentId, true);
        if (!$parent) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $parentId . ' не найден в таблице ' . $this->_tableName);
        }
        $chidren = $this->getNode($childrenId, true);
        if (!$chidren) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $childrenId . ' не найден в таблице ' . $this->_tableName);
        }
        
        $childrenLeftKey = $chidren[$this->_params->getColumnAlias('left_key')];
        $parentRightKey  = $parent[$this->_params->getColumnAlias('right_key')];
        $parentLeftKey   = $parent[$this->_params->getColumnAlias('left_key')];
        
        if (($childrenLeftKey < $parentRightKey)
            && ($childrenLeftKey > $parentLeftKey)) {
            // левый ключ попадет в диапазон родительских ключей
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Проверяет есть ли у узла вложенные узлы, основываясь
     * на разности левого и правого ключей
     *
     * @param int $nodeId идентификатор узла
     * @return boolean
     */
    public function hasChildrens($nodeId)
    {
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        
        if (($rightKey - $leftKey) > 1) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Создание корневого узла
     *
     * @return int идентификатор созданного узла
     */
    public function createRootNode()
    {
        return $this->createNodeUp();
    }
    
    /**
     * Создание узла в подчинении у заданного, в самом верху.
     *
     * @param int $parentId
     * @return int идентификатор созданного узла
     */
    public function createNodeUp($parentId = 0)
    {
        
        if ($parentId == 0) {    // Новый корневой узел
            // Вычисляем минимальный левый ключ
            $minLeftKey = $this->_sql->getMinLeftKey();

            $leftKey = $minLeftKey - 1;
            $level   = 0;
        } else {
            $node = $this->getNode($parentId, true);
            $leftKey = $node[$this->_params->getColumnAlias('left_key')];
            $level   = $node[$this->_params->getColumnAlias('level')];
        }
        
        // Обновляем родительскую ветку и ключи стоящие перед родительским узлом
        $this->_sql->createUpdate($leftKey);
        
        // Добавляем новый узел и получаем его идентификатор
        $id = $this->_sql->insertNode($leftKey + 1, $leftKey + 2, $level + 1);
        
        return $id;
    }
    
    /**
     * Создание узла в подчинении у заданного, в самом низу.
     *
     * @param int $parentId
     * @return int идентификатор созданного узла
     */
    public function createNodeDown($parentId = 0)
    {
       
        if ($parentId == 0) {    // Новый корневой узел
            // Вычисляем максимальный правый ключ
            $maxRightKey = $this->_sql->getMaxRightKey();

            $rightKey = $maxRightKey + 1;
            $level    = 0;
        } else {
            $node = $this->getNode($parentId, true);
            $rightKey = $node[$this->_params->getColumnAlias('right_key')];
            $level    = $node[$this->_params->getColumnAlias('level')];
        }
            
        // Обновляем родительскую ветку и ключи стоящие за родительским узлом
        $this->_sql->createUpdate($rightKey);

        // Добавляем новый узел и получаем его идентификатор
        $id = $this->_sql->insertNode($rightKey, $rightKey + 1, $level + 1);
        return $id;
    }
    
    /**
     * Создание узла рядом с заданым, ниже.
     *
     * @param int $parentId
     * @return int идентификатор созданного ниже
     */
    public function createBrotherDown($brotherId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($brotherId, true);
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        $level    = $node[$this->_params->getColumnAlias('level')];

        $this->_sql->createUpdate($rightKey, true);
        // Добавляем новый узел и получаем его идентификатор
        $id = $this->_sql->insertNode($rightKey + 1, $rightKey + 2, $level);
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $id;
    }
    
    /**
     * Создание узла рядом с заданым, выше.
     *
     * @param int $parentId
     * @return int идентификатор созданного узла
     */
    public function createBrotherUp($brotherId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($brotherId, true);
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $level    = $node[$this->_params->getColumnAlias('level')];

        $this->_sql->createUpdate($leftKey, true);
        // Добавляем новый узел и получаем его идентификатор
        $id = $this->_sql->insertNode($leftKey, $leftKey + 1, $level);
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $id;
    }
    
    /**
     * Вспомагательный метод, рекурсивно вызывающийся
     * для подготовки массива для вставки в бд, на основе
     * структурообразующего массива
     *
     * @param array $data структурный массив
     * @param int $level стартовый уровень, определяется родительским узлом
     * @param int $leftKey стартовый левый ключ, определяется родительским узлом
     * @param array $insertData ссылка на результирующий массив
     * @return новый правый ключ родительского элемента
     */
  protected function _prepareCreateNodesArray($data, $level, $leftKey, &$insertData, $subCategory='pages')
    {
        $treeIdName = $this->getParams()->getColumn('tree_id');
        $level++;
        foreach ($data as $k=>$item) {
            $itemMerge = $item;
            if(array_key_exists($subCategory, $itemMerge)) {
            unset($itemMerge[$subCategory]);
            }
            
            if (is_array($item) and array_key_exists($subCategory, $item)) {
                $rightKey = $this->_prepareCreateNodesArray($item[$subCategory], $level, $leftKey + 1, $insertData, $subCategory);
                $insertData[] = array_merge( array(
                    'left_key'  => $leftKey,
                    'right_key' => $rightKey,
                    $treeIdName   => $this->_params->getTreeId(),
                    'level'     => $level), $itemMerge
                );
                $leftKey = $rightKey+1;
            } else {
                 $rightKey = $leftKey + 1;
                 $insertData[] = array_merge( array(
                    'left_key'  => $leftKey,
                    'right_key' => $rightKey,
                    $treeIdName   => $this->_params->getTreeId(),
                    'level'     => $level
                     ), $itemMerge
                );
                $leftKey += 2;
            }
        }
        $rightKey++;
        return $rightKey;
    }
    

    
    /**
     * Создает узлы основываясь на переданной в массиве структуре.
     * Если нужно создать узлы в корневой части дерева $parentId устанавливаем в 0
     *
     * @param int $parentId узел в котором будет создан набор узлов
     * @param array $data
     * @param string $subCategory в каком ключе будут содержаться данные дочерних элементов
     * @params bool $up если false устанавливаем вставляемые узлы в самую нижнею позицию, если true то верх
     * @return array масив который был записан в таблицу данных.
     */
    public function createNodes($parentId, array $data = array(), $subCategory='pages', $up=true)
    {
         if (is_array($parentId)) {
            $data = $parentId;
            $parentId = 0;
        }
        
        // вычисляем общее количество узлов, которое будет вставлено
        $nodesCount = count($data);
        if ($nodesCount === 0) {
            throw new Arm_Db_Tree_Nested_Exception('Неверное значение '
                . 'параметра $data');
        }
        
        if($up) {
                // вычисляем данные родительского узла
            if ($parentId != 0) {
                $parent   = $this->getNode($parentId, true);
                $leftKey  = $parent[$this->_params->getColumnAlias('left_key')];
                $level    = $parent[$this->_params->getColumnAlias('level')];
            } else {
                // Вычисляем минимальный левый ключ
                $minLeftKey = $this->_sql->getMinLeftKey();
                $leftKey = $minLeftKey - 1;
                $level   = 0;
            }
            
        } else {
               if ($parentId != 0) {
                $parent   = $this->getNode($parentId, true);
                $rightKey      = $parent[$this->_params->getColumnAlias('right_key')];
                $level    = $parent[$this->_params->getColumnAlias('level')];
            } else {
                // Вычисляем минимальный левый ключ
                $maxRightKey     = $this->_sql->getMaxRightKey();
                $rightKey        = $maxRightKey + 1;
                $level           = 0;
            }
            
        }
        
        /**
         * Для вставки в бд подгoтавливаем массив,
         * содержащий данные всех новых узлов
         */
        $insertData = array();
        if ($up) {
            $this->_prepareCreateNodesArray($data, $level, $leftKey + 1, $insertData);
        } else {
            $this->_prepareCreateNodesArray($data, $level, $rightKey, $insertData);
        }
       
        $nodesCount = count($insertData); 
        // Осуществляем обновление узлов, с учетом новых
        if($up) {    
            $this->_sql->createUpdate($leftKey, false, $nodesCount);
        } else {
            $this->_sql->createUpdate($rightKey, false, $nodesCount);
        }
        // Осуществляем вставку в бд
        $result = $this->_sql->insertNodes($insertData);
        return $insertData;
    }
    
    /**
     * Смещает узел вверх по дереву, в рамках одного уровня и родителя
     *
     * @param int $nodeId идентификатор узла
     * @return boolean
     */
    public function movingUp($nodeId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey  = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        $level    = $node[$this->_params->getColumnAlias('level')];
        
        $result = $this->_sql->movingUp($leftKey, $rightKey, $level);
        
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $result;
    }

    /**
     * Смещает узел вниз по дереву, в рамках одного уровня и родителя
     *
     * @param int $nodeId идентификатор узла
     * @return boolean
     */
    public function movingDown($nodeId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey  = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        $level    = $node[$this->_params->getColumnAlias('level')];
        
        $result = $this->_sql->movingDown($leftKey, $rightKey, $level);
        
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $result;
    }
    
    /**
     * Перемещение рядом с другим узлом, выше
     *
     * @param int $nodeId идентификатор узла
     * @param int $brotherId
     * @return boolean
     */
    public function movingBrotherUp($nodeId, $brotherId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }

        // Определяем данные места перемещения - узла, за которым
        // будет располагаться перемещаемый узел
        $brotherNode = $this->getNode($brotherId, true);
        if (!$brotherId) {
            return false;
        }
        $leftKey         = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey        = $node[$this->_params->getColumnAlias('right_key')];
        $level           = $node[$this->_params->getColumnAlias('level')];
        $brotherLeftKey  = $brotherNode[$this->_params->getColumnAlias('left_key')];
        $brotherLevel    = $brotherNode[$this->_params->getColumnAlias('level')];
        
        $result = $this->_sql->movingBrotherUp($leftKey, $rightKey, $level,
            $brotherLeftKey, $brotherLevel);
        
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $result;
    }
    
    /**
     * Перемещение рядом с другим узлом, ниже
     *
     * @param int $nodeId идентификатор узла
     * @param int $brotherId
     * @return boolean
     */
    public function movingBrotherDown($nodeId, $brotherId)
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }

        // Определяем данные места перемещения - узла, перед которым
        // будет располагаться перемещаемый узел
        $brotherNode = $this->getNode($brotherId, true);
        if (!$brotherId) {
            return false;
        }
        $leftKey         = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey        = $node[$this->_params->getColumnAlias('right_key')];
        $level           = $node[$this->_params->getColumnAlias('level')];
        $brotherRightKey = $brotherNode[$this->_params->getColumnAlias('right_key')];
        $brotherLevel    = $brotherNode[$this->_params->getColumnAlias('level')];
        
        $result = $this->_sql->movingBrotherDown($leftKey, $rightKey, $level,
            $brotherRightKey, $brotherLevel);
        
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $result;
    }
    
    /**
     * Перемещение узла в подчинение другому, в начало списка
     *
     * @param int $nodeId идентификатор узла
     * @param int $parentId идентификатор родительского узла
     * @return boolean
     */
    public function movingUnderUp($nodeId, $parentId)
    {
            // Начало транзакции
        $this->_sql->beginTransaction();
        
        // Определяем данные перемещаемого узла
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey         = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey        = $node[$this->_params->getColumnAlias('right_key')];
        $level           = $node[$this->_params->getColumnAlias('level')];

        if ($parentId == 0) { // Перемещение в корень
            $near = 0;
            $levelNew = 1;
        } else {
            // Определяем данные узла в подчинение которому будет переносится узел
            $parent = $this->getNode($parentId, true);
            if (!$parent) {
                return false;
            }
            $parentLeftKey  = $parent[$this->_params->getColumnAlias('left_key')];
            $parentLevel    = $parent[$this->_params->getColumnAlias('level')];
            
            $near = $parentLeftKey;
            $levelNew = $parentLevel + 1;
        }
        
        $result = $this->_sql->moveNode($leftKey, $rightKey, $level, $levelNew, $near);
        // Конец транзакции
        $this->_sql->commitTransaction();

        return $result;
    }
    
    /**
     * Перемещение узла в подчинение другому, в конец списка
     *
     * @param int $nodeId идентификатор узла
     * @param int $parentId идентификатор родительского узла
     * @return boolean
     */
    public function movingUnderDown($nodeId, $parentId)
    {
            // Начало транзакции
        $this->_sql->beginTransaction();
        
        // Определяем данные перемещаемого узла
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            throw new Arm_Db_Tree_Nested_Exception('Узел с идентификатором '
                . $nodeId . ' не найден в таблице ' . $this->_tableName);
        }
        $leftKey         = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey        = $node[$this->_params->getColumnAlias('right_key')];
        $level           = $node[$this->_params->getColumnAlias('level')];

        if ($parentId == 0) { // Перемещение в корень
            $maxRightKey = $this->_sql->getMaxRightKey();
            $near = $maxRightKey;
            $levelNew = 1;
        } else {
            // Определяем данные узла в подчинение которому будет переносится узел
            $parent = $this->getNode($parentId, true);
            if (!$parent) {
                return false;
            }
            $parentRightKey = $parent[$this->_params->getColumnAlias('right_key')];
            $parentLevel    = $parent[$this->_params->getColumnAlias('level')];
            
            $near = $parentRightKey - 1;
            $levelNew = $parentLevel + 1;
        }
        
        $result = $this->_sql->moveNode($leftKey, $rightKey, $level, $levelNew, $near);
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return $result;
    }
    
    /**
     * Удаление узла и всех вложенных по идентификатору
     *
     * @param int $nodeId идентификатор узла
     * @return array|false вернет масив id узлов которые были удаленны или false если произошла ошибка.
     */
    public function delete($nodeId)
    {
        // Начало транзакции
       // $this->_sql->beginTransaction();
        
        $node = $this->getNode($nodeId, true);
        if (!$node) {
            return false;
        }
        $leftKey = $node[$this->_params->getColumnAlias('left_key')];
        $rightKey = $node[$this->_params->getColumnAlias('right_key')];
        // Получение идентификаторов удаляемых узлов, для удаления
        // записей из таблицы с данными
        $deletedNodes = $this->getChildrens($nodeId, true); // это все вложенные узлы
        $deletedIds = array($nodeId);                       // и сам узел
        foreach ($deletedNodes as $deletedNode) {
            $deletedIds[] = $deletedNode[$this->_params->getColumnAlias('id')];
        }
        // Удаление узла и всех вложенных
        $this->_sql->delete($leftKey, $rightKey);
        // Удаление данных из связанных таблиц
        $this->_sql->deleteCallback($deletedIds);
        // Обновление дерева
        $this->_sql->deleteUpdate($leftKey, $rightKey);
        // Конец транзакции
        //$this->_sql->commitTransaction();
        
        return $deletedIds;
    }
    
    /**
     * Удаление дерева
     *
     * @return boolean
     */
    public function deleteTree()
    {
        // Начало транзакции
        $this->_sql->beginTransaction();
        $treeId = $this->getParams()->getTreeId();
        $trees = $this->getAllTreesIds();
        if (!in_array($treeId, $trees)) {
            throw new Arm_Db_Tree_Nested_Exception('Дерево с идентификатором '
                . $treeId . ' не найдено в таблице ' . $this->_tableName);
        }
        // Получение идентификаторов удаляемых узлов, для удаления
        // записей из таблицы с данными
        $deletedNodes = $this->getTree($treeId, true);
        $deletedIds = array();
        foreach ($deletedNodes as $deletedNode) {
            $deletedIds[] = $deletedNode[$this->_params->getColumnAlias('id')];
        }
        
        // удаление дерева
        $this->_sql->deleteTree($treeId);
        // удаление данных из связанных таблиц
        $this->_sql->deleteCallback($deletedIds);
        // Конец транзакции
        $this->_sql->commitTransaction();
        
        return true;
    }
    
    /**
     * Полное удаление всех данных о деревьях в структурной и
     * связанных таблицах
     *
     * @return boolean
     */
    public function deleteTrees()
    {
        // Очистка структурной таблицы
        $this->_sql->deleteTrees();
        // Очистка связанных таблиц
        $this->_sql->deleteCallbackTrees();
        
        return true;
    }
    
	/**
     * Валидирует все деревья в текущей таблице.
     *
     * @throw Arm_Db_Tree_Nested_Exception если валидацию не прошло одно из деревьев.
     * @return boolean
     */
    public function validateTrees ()
    {
        $treesIds = $this->getAllTreesIds();
        foreach ($treesIds as $treeId) {
            if(false === $this->_sql->validateTree($treeId)) {
                throw new Arm_Db_Tree_Nested_Exception('Не прошло валидацию'
                    . ' дерево ' . $treeId);
            }
        }
        
        return true;
    }
    
    /**
     * Проверка одного дерева по его идентификатору.
     * Если не задан идентификатор - берется текущий.
     *
     * @param int $treeId идентификатор дерева для валидации
     * @return boolean true если дерево прошло валидацию, false если нет.
     */
    public function validateTree($treeId = null)
    {
      return $this->_sql->validateTree($treeId);
    }
    
    /**
     * Преобразование sql выборки в древовидную структуру, в массив массивов.
     *
     * @param array|Zend_Db_Select $data
     * @return array
     */
    public function getArray($data, $subCat = 'pages')
    {
        if(is_array($data) === FALSE and ($data instanceof Zend_Db_Select) === false ) {
            throw new Arm_Db_Tree_Nested_Exception('$data должен быть либо массивом либо объектом Zend_Db_Select');
        }
        if($data instanceof Zend_Db_Select) {
            $data = $this->_sql->getDb()->fetchAll($data);
        }
        
        $tree=Array();
        $levelAlias = $this->_params->getColumnAlias('level');
        $idAdlias   = $this->_params->getColumnAlias('id');
        
        foreach($data as $k=>$v)
        {
            $v[$levelAlias]=intval($v[$levelAlias]);
            if($v[$levelAlias]===(integer)$data[0][$levelAlias]){
                $tree[$v[$idAdlias]]=$v;
                $tree[$v[$idAdlias]][$subCat]=Array();
                $link[$v[$levelAlias]]=&$tree[$v[$idAdlias]][$subCat];
            }else{
                $link[$v[$levelAlias]-1][$v[$idAdlias]]=$v;
                $link[$v[$levelAlias]-1][$v[$idAdlias]][$subCat]=Array();
                $link[$v[$levelAlias]]=&$link[$v[$levelAlias]-1][$v[$idAdlias]][$subCat];
            }
        }
        return $tree;
    }
}